; Brother OP2 cpmish BIOS © 2020 David Given
; This file is distributable under the terms of the 2-clause BSD license.
; See COPYING.cpmish in the distribution root directory for more information.

    maclib cpm
    maclib cpmish
    maclib brotherwp1

    extrn SYSIN
    extrn SYSOUT
    extrn ADDAHL
    extrn DMAMEM

    extrn TTYPUTC
    extrn TTYPUT8
    extrn TTYPUT16
    extrn TTYPUTSI

    public SELDSKE
    public HOMEE
    public SETTRKE
    public SETSECE
    public SETDMAE
    public SECTRANE
    public READE
    public WRITEE
    public MOTORTICK
    public MOTORON
    public MOTOROFF

; This file contains the floppy drive handling code, and the deblocker. We
; don't use the standard deblocker because space is really at a premium and
; as we only have one drive we don't need the genericity of the standard
; deblocker.

    cseg

; Selects a drive, returning the address of the DPH in HL (or 0x0000 on
; error).
SELDSKE:
    ld hl, 0
    ld a, c
    or a
    ret nz                  ; return 0 if not drive 0
    ld hl, drive_a_dph
    ret

HOMEE:
    ld c, 0
    ; fall through
SETTRKE:
    ld a, c
    ld (current_track), a
    ret

SETSECE:
    ld a, c
    ld (current_sector), a
    ret

SETDMAE:
    ld (current_dma), bc
    ret

SECTRANE:
    ld h, b
    ld l, c
    ret

READE:
    xor a                   ; deblocking mode normal
    call getblock
    jr c, return_bios_error
    ; hl is pointer to record in buffer

    ld de, (current_dma)
    ld bc, 128
    ldir
    xor a
    ret

WRITEE:
    ld a, c                 ; set deblocking mode
    call getblock
    jr c, return_bios_error
    ; hl is pointer to record in buffer

    ld a, 1                 ; mark this sector as dirty
    ld (dirty), a

    ld de, (current_dma)
    ex de, hl
    ld bc, 128
    ldir

    ld a, (deblock_mode)    ; if deblock mode 1, flush the block now
    cp 1
    call z, flush_current_block
    xor a
    ret

return_bios_error:
    ld a, 1
    ret

MOTORON:
    ld hl, motor_status
    ld a, (hl)
    or a
    ld (hl), 0xff
    ret nz
    call SYSIN
    push ix
    push bc
    ld a, 1                  ; turn disk motor on
    rst 0x28
    pop bc
    pop ix
    call SYSOUT
    ret

MOTORTICK:
    ld hl, motor_status
    ld a, (hl)
    or a
    ret z
    dec (hl)
    jr z, really_motor_off
    ret

MOTOROFF:
    ld hl, motor_status
    ld a, (hl)
    or a
    ret z
    ld (hl), 0
really_motor_off:
    call SYSIN
    push ix
    push bc
    ld a, 2                  ; turn disk motor off
    rst 0x28
    pop bc
    pop ix
    call SYSOUT
    ret

    ; Changes the block in the deblocking buffer.
    ; Returns C on exit; HL contains the address of the CP/M sector.
getblock;
    ld (deblock_mode), a    ; save the deblocking mode

    ld hl, (current_track)  ; l = current track, h = garbage
    ld h, 12
    mlt hl
    ld a, (current_sector)  ; CP/M sector count!
    srl a                   ; native sector count
    call ADDAHL

    push hl
    ld bc, (current_block)
    or a                    ; clear carry
    sbc hl, bc              ; compare hl and bc
    pop hl
    jr z, calculate_physical_address ; block hasn't changed

    ld a, (dirty)           ; was the previous block dirty?
    or a                    ; clear carry
    call nz, flush_current_block ; HL is preserved
    ret c

    ld a, (deblock_mode)
    cp 2                    ; is this a fresh block?
    jr z, calculate_physical_address ; if so, skip the read

    push hl
    ld b, h                 ; new current block (zero based)
    ld c, l
    call read_syscall
    pop hl
    ret c

    ld (current_block), hl  ; update current block only once we know it worked
    ; fall through
calculate_physical_address:
    ld a, (current_sector)
    and 1                   ; bottom bit
    rrca                    ; ...goes to the top bit
    ld hl, disk_buffer      ; get offset into buffer
    call ADDAHL
    xor a                   ; clear carry (success)
    ret

; Writes the current block back. Returns C on error.
; Preserves HL but not A, DE, BC.

flush_current_block:
    push hl
    ld bc, (current_block)  ; old current block
    call write_syscall
    pop hl
    ret c                   ; exit on error

    xor a                   ; also clears carry
    ld (dirty), a           ; clear dirty flag
    ret

; On entry, BC = zero-based block to transfer.
read_syscall:
    call MOTORON
    call SYSIN

    ld hl, $f800
    inc bc                  ; block number must be one based
    push ix
    ld a, 6                 ; read sector
    rst 0x28
    pop ix

    ld b, fdd_read_dma_buffer_end - fdd_read_dma_buffer
    ld c, SAR0L
    ld hl, fdd_read_dma_buffer
    call DMAMEM
    
    call SYSOUT
    ret

; On entry, BC = zero-based block to transfer.
write_syscall:
    call MOTORON
    call SYSIN

    push bc
    ld b, fdd_write_dma_buffer_end - fdd_write_dma_buffer
    ld c, SAR0L
    ld hl, fdd_write_dma_buffer
    call DMAMEM
    pop bc
    
    ld hl, $f800
    inc bc                  ; block number must be one based
    push ix
    ld a, 7                 ; write sector
    rst 0x28
    pop ix

    call SYSOUT
    ret

    dseg

fdd_read_dma_buffer:
    dw 0x5800               ; SAR0 low
    db 0x02                 ; SAR0 high
    dw disk_buffer+0x7000   ; DAR0 low
    db 0x02                 ; DAR0 high
    dw 0x0100               ; BCR0
fdd_read_dma_buffer_end:

fdd_write_dma_buffer:
    dw disk_buffer+0x7000   ; SAR0 low
    db 0x02                 ; SAR0 high
    dw 0x5800               ; DAR0 low
    db 0x02                 ; DAR0 high
    dw 0x0100               ; BCR0
fdd_write_dma_buffer_end:

drive_a_dph:
    dw 0                    ; Sector translation vector
    dw 0, 0, 0              ; BDOS scratchpad
    dw dirbuf               ; Directory scratchpad
    dw drive_a_dpd          ; Drive parameter block
    dw drive_a_check_vector ; Disk change check vector
    dw drive_a_bitmap       ; Allocation bitmap

RESERVED_TRACKS = 3
DRIVE_A_BLOCKS = (39-RESERVED_TRACKS)*12*256 / 1024
drive_a_dpd:
    dw 12*2                 ; Number of CP/M sectors per track
    db 3, 7                 ; BSH/BLM for 1024-byte blocks
    db 0                    ; EXM for 1024-byte allocation units and <256 blocks
    dw DRIVE_A_BLOCKS-1     ; DSM
    dw 31                   ; DRM, one fewer than the number of directory entries
    db 0x80, 0x00           ; Initial allocation vector for one directory block
    dw 8                    ; Size of disk change vector: (DRM+1)/4
    dw RESERVED_TRACKS      ; Number of reserved tracks

drive_a_bitmap:
    ds (DRIVE_A_BLOCKS+7) / 8
drive_a_check_vector:
    ds 8
dirbuf:
    ds 128

deblock_mode:   db 0
dirty:          db 0
current_dma:    dw 0
current_sector: db 0
current_track:  db 0
current_block:  dw 0xffff
disk_buffer:    ds 256
motor_status:   db 0

; vim: ts=4 sw=4 et ft=asm

